/*

    This file is part of Hometree.

    Hometree is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Hometree is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Hometree.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2011,2012 Roman Held

*/
//-------------------------------------------------------------
//                     includes
//-------------------------------------------------------------
#include <QBuffer>
#include <QHostAddress>
#include <QDebug>
#include "htr/mansock.h"


//-------------------------------------------------------------
//                     constants
//-------------------------------------------------------------
#define STREAM_VERSION QDataStream::Qt_4_6

//-------------------------------------------------------------
//                   implementation
//-------------------------------------------------------------

//-------------------------------------------------------------
HtrManagedSocket::HtrManagedSocket(QObject *parent)
 : QObject(parent)
{
  mSocket    = NULL;
  mFrameSize = 0;
  mReadingStream = NULL;
  mWritingStream = NULL;
}

//-------------------------------------------------------------
HtrManagedSocket::HtrManagedSocket(QTcpSocket *socket, QObject *parent)
  : QObject(parent)
{
  mFrameSize = 0;
  mReadingStream = NULL;
  mWritingStream = NULL;

  Q_ASSERT(socket);
  Q_ASSERT(socket->thread() == thread());
  mSocket = socket;
  setup();
}

//-------------------------------------------------------------
HtrManagedSocket::~HtrManagedSocket()
{
  destroy();
}

//-------------------------------------------------------------
bool  HtrManagedSocket::connectToHost(const QString &hostName, quint16 port, int timeoutMs)
{
  connectToHost(hostName,port);
  return mSocket->waitForConnected(timeoutMs);
}

//-------------------------------------------------------------
void  HtrManagedSocket::connectToHost(const QString &hostName, quint16 port)
{
  if (mSocket) {
    mSocket->disconnect(this);
    mSocket->deleteLater();
  }
  mSocket = new QTcpSocket(this);
  setup();
  mSocket->connectToHost(hostName,port);
}

//-------------------------------------------------------------
void  HtrManagedSocket::disconnectFromHost()
{
  Q_ASSERT(!mSocket);
  mSocket->disconnectFromHost();
}

//-------------------------------------------------------------
void  HtrManagedSocket::setSocket(QTcpSocket *socket)
{
  Q_ASSERT(socket);  
  Q_ASSERT(socket->thread() == thread());
  mSocket = socket;
  mSocket->setParent(this); // den Klauen des QTcpServers entreissen..
  setup();
}

//-------------------------------------------------------------
QTcpSocket   *HtrManagedSocket::socket() const
{
  return mSocket;
}

//-------------------------------------------------------------
QAbstractSocket::SocketState   HtrManagedSocket::state() const
{
  return mSocket ? mSocket->state() : QAbstractSocket::UnconnectedState;
}

//-------------------------------------------------------------
bool HtrManagedSocket::isConnected() const
{
  return (mSocket && (mSocket->state() == QAbstractSocket::ConnectedState));
}

//-------------------------------------------------------------
void HtrManagedSocket::close()
{
  destroy();
}

//-------------------------------------------------------------
bool HtrManagedSocket::flush()
{
  Q_ASSERT(mSocket);
  bool ret = mSocket->flush();
  if (ret)
    mSocket->waitForBytesWritten(10000); // Timeout?
  return ret;
}

//-------------------------------------------------------------
QString HtrManagedSocket::ident() const
{
  Q_ASSERT(mSocket);
  return QString("%1:%2").arg(mSocket->peerAddress().toString()).arg(mSocket->peerPort());
}
     
//-------------------------------------------------------------
QDataStream&   HtrManagedSocket::readStream()
{
  Q_ASSERT(mReadingPaket.count() > 0);
  Q_ASSERT(mReadingStream);
  return *mReadingStream;
}

//-------------------------------------------------------------
bool HtrManagedSocket::next()
{
  if (!mPakets.count())
    return false;
  
  mReadingPaket = mPakets.takeFirst();
  // reset des Streams:
  if (mReadingStream)
    delete mReadingStream;

  mReadingStream = new QDataStream(&mReadingPaket,QIODevice::ReadOnly);
  // mReadingStream->setVersion(STREAM_VERSION); 
  setupStream(*mReadingStream);

  return true;
}

//-------------------------------------------------------------
int HtrManagedSocket::paketsAvailable() const
{
  return mPakets.count();
}

//-------------------------------------------------------------
QDataStream& HtrManagedSocket::prepareStream()
{
  if (mWritingStream)
    delete mWritingStream;
  mWritingStream = new QDataStream(&mWritingPaket,QIODevice::WriteOnly);
  setupStream(*mWritingStream);
  return *mWritingStream;
}

//-------------------------------------------------------------
void HtrManagedSocket::setupStream(QDataStream &s)
{
  s.setVersion(STREAM_VERSION);
}

//-------------------------------------------------------------
bool HtrManagedSocket::writeStream()
{
  Q_ASSERT(mWritingPaket.count() > 0);

  return writeByteArray(mWritingPaket);
}

//-------------------------------------------------------------
bool HtrManagedSocket::writeByteArray(const QByteArray &buffer)
{
  Q_ASSERT(buffer.size() > 0);
 
  if (!isConnected())
    return false;

  qint32 frameSize = buffer.size();

  // TODO: Endianness
  mSocket->write((char*)&frameSize,sizeof(qint32));
  mSocket->write(buffer);
  return true;
}

//-------------------------------------------------------------
void HtrManagedSocket::processData()
{
  Q_ASSERT(mSocket);
 
  do {
    emit reading();
    if (mFrameSize == 0) {
      Q_ASSERT(mFrame.size() == 0);
      if (mSocket->bytesAvailable() < (int)sizeof(mFrameSize))
        break;  
      mSocket->read((char*)&mFrameSize, sizeof(mFrameSize));
      // TODO: Endianness
      Q_ASSERT(mFrameSize > 0);
    }

    // Auf keinen Fall ueber Frame hinaus lesen...
    qint32 expectedSize = mFrameSize - mFrame.size();
    mFrame.append(mSocket->read(expectedSize));
    if (mFrame.size() == mFrameSize) {
      mPakets << mFrame;
      mFrame.clear();
      mFrameSize = 0;
      emit readyRead();
    }
  } while (mSocket->bytesAvailable());

  /*
  if (oldPaketCount != mPakets.count()) {
    emit readyRead();
  }
  */
}

//-------------------------------------------------------------
void HtrManagedSocket::processState(QAbstractSocket::SocketState st)
{
  emit hasConnected(st == QAbstractSocket::ConnectedState);
}

//-------------------------------------------------------------
void HtrManagedSocket::destroy()
{
  mPakets.clear();
  mFrameSize = 0;
  mFrame.clear();

  if (mReadingStream) {
    delete mReadingStream;
    mReadingStream = NULL;
  }
  if (mWritingStream) {
    delete mWritingStream;
    mWritingStream = NULL;
  }

  if (!mSocket)
    return;

  mSocket->disconnect(this);
  mSocket->deleteLater();
  mSocket = 0;
  emit stateChanged(QAbstractSocket::UnconnectedState);
}

//-------------------------------------------------------------
void HtrManagedSocket::setup()
{
  Q_ASSERT(mSocket);

  // Signal-Redirects:
  connect(mSocket, SIGNAL(error(QAbstractSocket::SocketError)), 
          this, SIGNAL(error(QAbstractSocket::SocketError)));
  connect(mSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), 
          this, SIGNAL(stateChanged(QAbstractSocket::SocketState)));
  connect(this, SIGNAL(stateChanged(QAbstractSocket::SocketState)), 
          this, SLOT(processState(QAbstractSocket::SocketState)));
  connect(mSocket, SIGNAL(bytesWritten(qint64)), 
          this, SIGNAL(writing()));

  // private slots:
  connect(mSocket, SIGNAL(readyRead()), 
          this, SLOT(processData()));

  emit stateChanged(mSocket->state());
}

